<script setup>
import { computed, ref, useComponentRouter, onMounted, onUnmounted, watch } from "#imports";
import { useWorkflowAnalysis } from "../../composables/useWorkflowAnalysis";
const { sortNodesByLevel, analyzeWorkflow } = useWorkflowAnalysis();
const { push, route } = useComponentRouter();
const props = defineProps({
  runId: { type: String, required: false }
});
const runId = computed(() => props.runId || route.value.params.id);
const isStateSlideoverOpen = ref(false);
const isStreamSlideoverOpen = ref(false);
const isCancelSlideoverOpen = ref(false);
const isDeleteModalOpen = ref(false);
const cancelPending = ref(false);
const cancelError = ref(null);
const cancelResult = ref(null);
const deletePending = ref(false);
const deleteError = ref(null);
function useWorkflowRunDetail(runIdRef) {
  const status2 = ref(null);
  const statusPending = ref(false);
  const statusError = ref(null);
  let inFlight = null;
  async function refreshStatus(options) {
    if (inFlight) {
      await inFlight;
      return;
    }
    const silent = Boolean(options?.silent);
    const run = async () => {
      if (!silent) {
        statusPending.value = true;
      }
      statusError.value = null;
      try {
        status2.value = await $fetch("/api/_workflows/status", {
          params: { run_id: runIdRef.value }
        });
      } catch (error2) {
        statusError.value = error2;
      } finally {
        if (!silent) {
          statusPending.value = false;
        }
      }
    };
    inFlight = run();
    try {
      await inFlight;
    } finally {
      inFlight = null;
    }
  }
  async function refreshAll2() {
    await refreshStatus();
  }
  onMounted(() => {
    void refreshAll2();
  });
  watch(runIdRef, () => {
    void refreshAll2();
  });
  return {
    status: status2,
    statusPending,
    statusError,
    refreshStatus,
    refreshAll: refreshAll2
  };
}
const {
  status,
  statusPending: pending,
  statusError: error,
  refreshStatus: refresh,
  refreshAll: refreshAllData
} = useWorkflowRunDetail(runId);
const definition = computed(() => status.value?.definition);
function resultStateForNodeCheckpoint(nodeUid, checkpoint) {
  const backendMode = status.value?.node_result_modes?.[nodeUid];
  const backendState = status.value?.node_result_states?.[nodeUid];
  const resultAvailable = Boolean(checkpoint?.result_ref);
  const hasRef = Boolean(checkpoint?.result_ref);
  const resultState = backendState || (hasRef ? "ready" : "pending");
  return {
    declaredMode: backendMode?.declared_mode || "memory",
    effectiveMode: backendMode?.effective_mode || "memory",
    onMemoryFail: backendMode?.on_memory_fail,
    resultAvailable,
    resultState
  };
}
let refreshInterval = null;
onMounted(() => {
  refreshInterval = setInterval(() => {
    if (status.value?.status === "running" || status.value?.status === "awaiting_nodes" || status.value?.status === "awaiting") {
      refresh({ silent: true });
    }
  }, 1e3);
});
onUnmounted(() => {
  if (refreshInterval) clearInterval(refreshInterval);
});
const normalizedStatus = computed(() => {
  const currentStatus = status.value?.status;
  if (currentStatus === "done") return "completed";
  if (currentStatus === "error") return "failed";
  if (currentStatus === "awaiting_nodes" || currentStatus === "awaiting") return "running";
  return currentStatus;
});
function topoSortSubset(nodeIds, nodeDefs, fallbackOrder) {
  const subset = new Set(nodeIds);
  const indegree = /* @__PURE__ */ new Map();
  const forward = /* @__PURE__ */ new Map();
  for (const id of nodeIds) {
    indegree.set(id, 0);
    forward.set(id, []);
  }
  for (const id of nodeIds) {
    const deps = Array.isArray(nodeDefs[id]?.depends_on) ? nodeDefs[id].depends_on : [];
    for (const dep of deps) {
      if (!subset.has(dep)) continue;
      forward.get(dep)?.push(id);
      indegree.set(id, (indegree.get(id) || 0) + 1);
    }
  }
  const rank = /* @__PURE__ */ new Map();
  fallbackOrder.forEach((id, idx) => rank.set(id, idx));
  const ready = nodeIds.filter((id) => (indegree.get(id) || 0) === 0);
  ready.sort((a, b) => (rank.get(a) ?? Number.MAX_SAFE_INTEGER) - (rank.get(b) ?? Number.MAX_SAFE_INTEGER));
  const out = [];
  while (ready.length > 0) {
    const id = ready.shift();
    out.push(id);
    for (const nextId of forward.get(id) || []) {
      indegree.set(nextId, (indegree.get(nextId) || 0) - 1);
      if ((indegree.get(nextId) || 0) === 0) {
        ready.push(nextId);
      }
    }
    ready.sort((a, b) => (rank.get(a) ?? Number.MAX_SAFE_INTEGER) - (rank.get(b) ?? Number.MAX_SAFE_INTEGER));
  }
  if (out.length === nodeIds.length) return out;
  return [...nodeIds].sort((a, b) => (rank.get(a) ?? Number.MAX_SAFE_INTEGER) - (rank.get(b) ?? Number.MAX_SAFE_INTEGER));
}
const loopGroups = computed(() => {
  const nodeDefs = definition.value?.nodes || {};
  const nodeIds = Object.keys(nodeDefs);
  if (nodeIds.length === 0) return { groups: [], byNodeId: {} };
  const fallbackOrder = sortNodesByLevel(nodeDefs);
  const loopNodeIds = nodeIds.filter((id) => Boolean(nodeDefs[id]?.fanout));
  if (loopNodeIds.length === 0) return { groups: [], byNodeId: {} };
  const sigByNode = /* @__PURE__ */ new Map();
  for (const id of loopNodeIds) {
    const fanout = nodeDefs[id]?.fanout || {};
    const over = String(fanout.over || "");
    const mode = fanout.mode === "sequential" ? "sequential" : fanout.mode === "batch" ? "batch" : "parallel";
    const batchSize = Number(fanout.batchSize);
    const sigBatchSize = Number.isFinite(batchSize) && batchSize > 0 ? batchSize : 0;
    sigByNode.set(id, `${over}::${mode}::${sigBatchSize}`);
  }
  const adjacency = /* @__PURE__ */ new Map();
  for (const id of loopNodeIds) adjacency.set(id, /* @__PURE__ */ new Set());
  for (const id of loopNodeIds) {
    const deps = Array.isArray(nodeDefs[id]?.depends_on) ? nodeDefs[id].depends_on : [];
    for (const dep of deps) {
      if (!adjacency.has(dep)) continue;
      if (sigByNode.get(id) !== sigByNode.get(dep)) continue;
      adjacency.get(id)?.add(dep);
      adjacency.get(dep)?.add(id);
    }
  }
  const visited = /* @__PURE__ */ new Set();
  const components = [];
  for (const start of loopNodeIds) {
    if (visited.has(start)) continue;
    const queue = [start];
    visited.add(start);
    const component = [];
    while (queue.length > 0) {
      const id = queue.shift();
      component.push(id);
      for (const nextId of adjacency.get(id) || []) {
        if (visited.has(nextId)) continue;
        visited.add(nextId);
        queue.push(nextId);
      }
    }
    components.push(component);
  }
  const groups = components.flatMap((component, index) => {
    const first = component[0];
    if (!first) return [];
    const fanout = nodeDefs[first]?.fanout || {};
    const mode = fanout.mode === "sequential" ? "sequential" : fanout.mode === "batch" ? "batch" : "parallel";
    const batchSizeValue = Number(fanout.batchSize);
    const over = String(fanout.over || "");
    const orderedNodes = topoSortSubset(component, nodeDefs, fallbackOrder);
    const itemInputMode = fanout.itemReturnType === "store" ? "store" : "memory";
    const perNodeResultModes = orderedNodes.map((nodeId) => {
      const returnType = String(nodeDefs[nodeId]?.result?.returnType || "memory");
      return returnType === "store" ? "store" : "memory";
    });
    const hasStoreResults = perNodeResultModes.includes("store");
    const hasMemoryResults = perNodeResultModes.includes("memory");
    const itemResultMode = hasStoreResults && hasMemoryResults ? "mixed" : hasStoreResults ? "store" : "memory";
    const group = {
      id: `loop-${index + 1}`,
      mode,
      batchSize: Number.isFinite(batchSizeValue) && batchSizeValue > 0 ? batchSizeValue : void 0,
      over,
      itemInputMode,
      itemResultMode,
      nodeIds: orderedNodes,
      label: orderedNodes.join(" -> ")
    };
    return [group];
  });
  groups.sort((a, b) => {
    const firstA = fallbackOrder.indexOf(a.nodeIds[0] || "");
    const firstB = fallbackOrder.indexOf(b.nodeIds[0] || "");
    return firstA - firstB;
  });
  const byNodeId = {};
  for (const group of groups) {
    for (const nodeId of group.nodeIds) {
      byNodeId[nodeId] = group;
    }
  }
  return { groups, byNodeId };
});
const flowMeta = computed(() => {
  if (!definition.value?.nodes) return null;
  const analyzed = analyzeWorkflow(definition.value.nodes);
  const steps = {};
  Object.entries(definition.value.nodes).forEach(([id, node]) => {
    const loopGroup = loopGroups.value.byNodeId[id];
    steps[id] = {
      name: id,
      label: node.label,
      workerId: node.function?.id,
      runtime: node.function?.runtime,
      dependsOn: node.depends_on || [],
      queue: node.function?.queue || "default",
      engineRetryMax: node.function?.engine_retry?.max_attempts,
      runtype: node.runtype || "task",
      emits: node.emits || [],
      isLoop: Boolean(loopGroup),
      loopOver: loopGroup?.over,
      loopMode: loopGroup?.mode || "parallel",
      loopBatchSize: loopGroup?.batchSize,
      loopGroupId: loopGroup?.id,
      loopGroupSize: loopGroup?.nodeIds.length || 0,
      loopPipeline: loopGroup?.label
    };
  });
  let entry;
  if (analyzed.levels[0]?.length === 1) {
    const entryId = analyzed.levels[0][0];
    if (entryId) {
      const node = definition.value.nodes[entryId];
      entry = {
        step: entryId,
        label: node.label,
        queue: node.function?.queue || "default",
        engineRetryMax: node.function?.engine_retry?.max_attempts,
        workerId: node.function.id,
        runtime: node.function.runtime,
        runtype: node.runtype || "task",
        emits: node.emits || []
      };
    }
  }
  return {
    id: runId.value,
    entry,
    steps,
    loopGroups: loopGroups.value.groups,
    analyzed
  };
});
const stepStates = computed(() => {
  const out = {};
  const nodeStates = status.value?.nodes ?? {};
  Object.entries(nodeStates).forEach(([id, nodeStatus]) => {
    let uiStatus = nodeStatus.state || nodeStatus;
    if (typeof uiStatus === "string") {
      if (uiStatus === "done") uiStatus = "completed";
      else if (uiStatus === "error" || uiStatus === "failed") uiStatus = "failed";
      else if (uiStatus === "cancelled" || uiStatus === "canceled") uiStatus = "canceled";
      else if (uiStatus === "active" || uiStatus === "queued" || uiStatus === "running") uiStatus = "running";
    }
    const nodeResultState = resultStateForNodeCheckpoint(id, nodeStatus);
    out[id] = {
      status: uiStatus,
      error: nodeStatus.result_error,
      result: nodeStatus.result_ref,
      result_mode_declared: nodeResultState.declaredMode,
      result_mode_effective: nodeResultState.effectiveMode,
      result_on_memory_fail: nodeResultState.onMemoryFail,
      result_available: nodeResultState.resultAvailable,
      result_state: nodeResultState.resultState,
      pending_at: nodeStatus.pending_at,
      completed_at: nodeStatus.completed_at,
      worker_name: nodeStatus.worker_name,
      retries: nodeStatus.retries
    };
  });
  Object.entries(definition.value?.nodes ?? {}).forEach(([baseId, nodeDef]) => {
    if (!nodeDef?.fanout) return;
    const children = Object.entries(nodeStates).filter(([id]) => id.startsWith(`${baseId}#`));
    if (children.length === 0) return;
    const childStates = children.map(([, cp]) => String(cp?.state || "").toLowerCase());
    const childErrors = children.map(([, cp]) => cp?.result_error).filter((msg) => typeof msg === "string" && msg.length > 0);
    const retries = children.reduce((sum, [, cp]) => sum + Number(cp?.retries || 0), 0);
    const childResultStates = children.map(([uid, cp]) => resultStateForNodeCheckpoint(uid, cp));
    let aggregated = "idle";
    if (childStates.some((s) => s === "failed" || s === "error")) aggregated = "failed";
    else if (childStates.some((s) => s === "running" || s === "active" || s === "queued" || s === "pending")) aggregated = "running";
    else if (childStates.some((s) => s === "cancelled" || s === "canceled")) aggregated = "canceled";
    else if (childStates.length > 0 && childStates.every((s) => s === "done" || s === "completed")) aggregated = "completed";
    const childPending = children.map(([, cp]) => Number(cp?.pending_at || 0)).filter((v) => v > 0);
    const childCompleted = children.map(([, cp]) => Number(cp?.completed_at || 0)).filter((v) => v > 0);
    out[baseId] = {
      ...out[baseId] || {},
      status: aggregated,
      retries,
      error: childErrors[0],
      result_mode_declared: childResultStates[0]?.declaredMode || out[baseId]?.result_mode_declared,
      result_mode_effective: childResultStates[0]?.effectiveMode || out[baseId]?.result_mode_effective,
      result_on_memory_fail: childResultStates[0]?.onMemoryFail || out[baseId]?.result_on_memory_fail,
      result_available: childResultStates.some((item) => item.resultAvailable),
      result_state: childResultStates.some((item) => item.resultAvailable) ? "ready" : childResultStates.some((item) => item.resultState === "pruned") ? "pruned" : "pending",
      pending_at: childPending.length > 0 ? Math.min(...childPending) : out[baseId]?.pending_at,
      completed_at: childCompleted.length > 0 ? Math.max(...childCompleted) : out[baseId]?.completed_at
    };
  });
  return out;
});
const stepList = computed(() => {
  if (!definition.value?.nodes) return [];
  const ordered = sortNodesByLevel(definition.value.nodes);
  const nodeStates = status.value?.nodes ?? {};
  const nodeStateEntries = Object.entries(nodeStates);
  const groupsById = Object.fromEntries(loopGroups.value.groups.map((group) => [group.id, group]));
  const groupIdByNodeId = Object.fromEntries(
    Object.entries(loopGroups.value.byNodeId).map(([nodeId, group]) => [nodeId, group.id])
  );
  const groupResultCounts = {};
  const loopChildResultUidsByBase = {};
  for (const [uid, cp] of nodeStateEntries) {
    const base = uid.split("#")[0] || "";
    const groupId = groupIdByNodeId[base];
    if (!groupId) {
      if (cp?.result_ref) {
        if (!loopChildResultUidsByBase[base]) loopChildResultUidsByBase[base] = [];
        loopChildResultUidsByBase[base].push(uid);
      }
      continue;
    }
    if (!groupResultCounts[groupId]) {
      groupResultCounts[groupId] = { total: 0, store: 0, memory: 0, pruned: 0 };
    }
    groupResultCounts[groupId].total += Number(Boolean(cp?.result_ref));
    const resultState = resultStateForNodeCheckpoint(uid, cp);
    if (resultState.effectiveMode === "store") {
      groupResultCounts[groupId].store += Number(resultState.resultAvailable);
    } else if (resultState.effectiveMode === "memory") {
      groupResultCounts[groupId].memory += Number(resultState.resultAvailable);
      groupResultCounts[groupId].pruned += Number(resultState.resultState === "pruned");
    }
    if (cp?.result_ref) {
      if (!loopChildResultUidsByBase[base]) loopChildResultUidsByBase[base] = [];
      loopChildResultUidsByBase[base].push(uid);
    }
  }
  for (const uidList of Object.values(loopChildResultUidsByBase)) {
    uidList.sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
  }
  const insertedGroups = /* @__PURE__ */ new Set();
  const out = [];
  for (const id of ordered) {
    const state = stepStates.value[id];
    const node = definition.value.nodes[id];
    const group = loopGroups.value.byNodeId[id];
    if (group && !insertedGroups.has(group.id)) {
      const groupLoopNodeStats = group.nodeIds.map((memberId) => status.value?.loop_stats?.[memberId]).filter(Boolean);
      const totalItems = groupLoopNodeStats.reduce((max, entry) => Math.max(max, Number(entry.total_items || 0)), 0);
      const completedItemsPipeline = groupLoopNodeStats.reduce((min, entry) => {
        const value = Number(entry.completed_items || 0);
        return min === null ? value : Math.min(min, value);
      }, null) ?? 0;
      const completedItemsAny = groupLoopNodeStats.reduce((max, entry) => Math.max(max, Number(entry.completed_items || 0)), 0);
      const runningItems = groupLoopNodeStats.reduce((sum, entry) => sum + Number(entry.running_items || 0), 0);
      const failedItems = groupLoopNodeStats.reduce((sum, entry) => sum + Number(entry.failed_items || 0), 0);
      const pendingItems = groupLoopNodeStats.reduce((sum, entry) => sum + Number(entry.pending_items || 0), 0);
      const activeIndexCandidates = groupLoopNodeStats.map((entry) => Number(entry.active_index)).filter((v) => Number.isFinite(v) && v >= 0);
      const activeIndex = activeIndexCandidates.length > 0 ? Math.min(...activeIndexCandidates) : null;
      const memberStates = group.nodeIds.map((memberId) => stepStates.value[memberId]).filter(Boolean);
      const statuses = memberStates.map((memberState) => String(memberState?.status || "").toLowerCase());
      const loopResultCounts = groupResultCounts[group.id] || { total: 0, store: 0, memory: 0, pruned: 0 };
      let groupStatus = "idle";
      if (statuses.some((s) => s === "failed" || s === "error")) groupStatus = "failed";
      else if (statuses.some((s) => s === "running" || s === "queued" || s === "active" || s === "pending")) groupStatus = "running";
      else if (statuses.length > 0 && statuses.every((s) => s === "completed" || s === "done")) groupStatus = "completed";
      const groupRetries = memberStates.reduce((sum, memberState) => sum + Number(memberState?.retries || 0), 0);
      out.push({
        key: `loop-group:${group.id}`,
        status: groupStatus,
        retries: groupRetries,
        isLoopGroup: true,
        loopGroupId: group.id,
        loopOver: group.over,
        loopMode: group.mode,
        loopBatchSize: group.batchSize,
        loopItemInputMode: group.itemInputMode,
        loopItemResultMode: group.itemResultMode,
        loopPipeline: group.label,
        loopSize: group.nodeIds.length,
        loopItemsTotal: totalItems,
        loopItemsDone: completedItemsAny,
        loopItemsPipelineDone: completedItemsPipeline,
        loopItemsRunning: runningItems,
        loopItemsFailed: failedItems,
        loopItemsPending: pendingItems,
        loopActiveIndex: activeIndex,
        canInspectResult: loopResultCounts.total > 0,
        loopResultStoreCount: loopResultCounts.store,
        loopResultMemoryCount: loopResultCounts.memory,
        loopResultPrunedCount: loopResultCounts.pruned
      });
      insertedGroups.add(group.id);
    }
    const loopChildResultUids = loopChildResultUidsByBase[id] || [];
    out.push({
      key: id,
      label: node?.label,
      status: state?.status || "idle",
      error: state?.error,
      result: state?.result,
      resultModeDeclared: state?.result_mode_declared,
      resultModeEffective: state?.result_mode_effective,
      resultOnMemoryFail: state?.result_on_memory_fail,
      resultAvailable: state?.result_available,
      resultState: state?.result_state,
      retries: state?.retries,
      isLoop: false,
      loopOver: group?.over,
      loopMode: group?.mode || "parallel",
      loopBatchSize: group?.batchSize,
      loopGroupId: group?.id,
      loopSize: group?.nodeIds.length || 0,
      loopPipeline: groupsById[group?.id || ""]?.label,
      inLoopGroup: Boolean(group),
      isLoopLeader: Boolean(group?.nodeIds[0] === id),
      functionId: node?.function?.id,
      isVarStep: node?.function?.id === "workflow::internal-var-set",
      canInspectResult: Boolean(state?.result) || loopChildResultUids.length > 0,
      loopChildResultUids
    });
  }
  return out;
});
const runResultStorageMode = computed(() => {
  return status.value?.output_result_mode_effective || "none";
});
const resultOverview = computed(() => {
  const nodeStates = status.value?.nodes || {};
  let readyMemory = 0;
  let readyStore = 0;
  let readyStream = 0;
  let prunedMemory = 0;
  let pending2 = 0;
  for (const [uid, cp] of Object.entries(nodeStates)) {
    const state = resultStateForNodeCheckpoint(uid, cp);
    if (state.resultState === "pending") {
      pending2 += 1;
      continue;
    }
    if (state.resultState === "pruned") {
      prunedMemory += 1;
      continue;
    }
    if (state.effectiveMode === "store") readyStore += 1;
    else if (state.effectiveMode === "stream") readyStream += 1;
    else readyMemory += 1;
  }
  return {
    readyMemory,
    readyStore,
    readyStream,
    prunedMemory,
    pending: pending2
  };
});
const selectedStep = ref(null);
const isResultSlideoverOpen = ref(false);
const selectedResultStepKey = ref(null);
const selectedResultNodeUid = ref(null);
const resultNodeUidOptions = ref([]);
const resultPending = ref(false);
const resultError = ref(null);
const selectedNodeResult = ref(null);
function baseStepName(stepName) {
  if (!stepName) return null;
  return String(stepName).split("#")[0] || null;
}
const loopGroupNodeIdsByKey = computed(() => {
  const map = {};
  for (const group of loopGroups.value.groups) {
    map[`loop-group:${group.id}`] = group.nodeIds;
  }
  return map;
});
const selectedStepNodeIds = computed(() => {
  const current = selectedStep.value;
  if (!current || !current.startsWith("loop-group:")) return [];
  return loopGroupNodeIdsByKey.value[current] || [];
});
function stepMatchesSelection(stepName, selection) {
  if (!selection) return true;
  const base = baseStepName(stepName);
  if (!base) return false;
  if (selection.startsWith("loop-group:")) {
    const members = loopGroupNodeIdsByKey.value[selection] || [];
    return members.includes(base);
  }
  return base === selection;
}
function candidateResultUidsForStep(stepKey) {
  const nodeStates = status.value?.nodes ?? {};
  if (stepKey.startsWith("loop-group:")) {
    const members = loopGroupNodeIdsByKey.value[stepKey] || [];
    return Object.entries(nodeStates).filter(([uid, cp]) => {
      const base = uid.split("#")[0];
      return members.includes(base || "") && Boolean(cp?.result_ref);
    }).map(([uid]) => uid).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
  }
  const direct = nodeStates[stepKey];
  if (direct?.result_ref) return [stepKey];
  return Object.entries(nodeStates).filter(([uid, cp]) => uid.startsWith(`${stepKey}#`) && Boolean(cp?.result_ref)).map(([uid]) => uid).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
}
async function fetchNodeResult(nodeUid) {
  resultPending.value = true;
  resultError.value = null;
  try {
    const response = await $fetch("/api/_workflows/node-result", {
      params: {
        run_id: runId.value,
        node_uid: nodeUid
      }
    });
    selectedNodeResult.value = response?.result ?? null;
  } catch (error2) {
    resultError.value = error2?.data?.statusMessage || error2?.message || "Result loading failed";
    selectedNodeResult.value = null;
  } finally {
    resultPending.value = false;
  }
}
async function openResultSlideover(stepKey) {
  selectedResultStepKey.value = stepKey;
  resultNodeUidOptions.value = candidateResultUidsForStep(stepKey);
  selectedResultNodeUid.value = resultNodeUidOptions.value[0] || null;
  selectedNodeResult.value = null;
  resultError.value = null;
  isResultSlideoverOpen.value = true;
  if (selectedResultNodeUid.value) {
    await fetchNodeResult(selectedResultNodeUid.value);
  }
}
watch(selectedResultNodeUid, (nextUid, prevUid) => {
  if (!isResultSlideoverOpen.value) return;
  if (!nextUid || nextUid === prevUid) return;
  void fetchNodeResult(nextUid);
});
async function refreshAll() {
  await refresh();
}
async function openStateSlideover() {
  isStateSlideoverOpen.value = true;
}
async function openStreamSlideover() {
  isStreamSlideoverOpen.value = true;
}
function openCancelSlideover() {
  isCancelSlideoverOpen.value = true;
}
function openDeleteModal() {
  if (!isRunTerminal.value || deletePending.value) return;
  deleteError.value = null;
  isDeleteModalOpen.value = true;
}
async function confirmDeleteRun() {
  if (!isRunTerminal.value || deletePending.value) return;
  deletePending.value = true;
  deleteError.value = null;
  try {
    await $fetch("/api/_workflows/delete", {
      method: "POST",
      body: { run_id: runId.value }
    });
    isDeleteModalOpen.value = false;
    await push("/workflows/runs");
  } catch (error2) {
    deleteError.value = error2?.data?.statusMessage || error2?.message || "Delete failed";
  } finally {
    deletePending.value = false;
  }
}
async function cancelRun() {
  cancelPending.value = true;
  cancelError.value = null;
  try {
    cancelResult.value = await $fetch("/api/_workflows/stop", {
      method: "POST",
      body: { run_id: runId.value }
    });
    await refreshAll();
  } catch (error2) {
    cancelError.value = error2?.data?.statusMessage || error2?.message || "Cancel failed";
  } finally {
    cancelPending.value = false;
  }
}
const cancelCleanupAttempted = computed(() => cancelResult.value?.queue_cleanup_attempted ?? 0);
const cancelCleanupSucceeded = computed(() => cancelResult.value?.queue_cleanup_succeeded ?? 0);
const cancelCleanupErrors = computed(() => cancelResult.value?.queue_cleanup_errors ?? {});
const cancelCleanupErrorEntries = computed(() => Object.entries(cancelCleanupErrors.value));
const cancelCleanupHasErrors = computed(() => cancelCleanupErrorEntries.value.length > 0);
const cancelCleanupRate = computed(() => {
  const attempted = cancelCleanupAttempted.value;
  if (attempted <= 0) return 100;
  return Math.round(cancelCleanupSucceeded.value / attempted * 100);
});
const cancelCheckedQueues = computed(() => cancelResult.value?.checked_queues ?? []);
const cancelTrackedReceiptIds = computed(() => cancelResult.value?.tracked_receipt_ids ?? []);
const isRunCancelled = computed(() => normalizedStatus.value === "cancelled");
const isRunTerminal = computed(() => {
  const current = String(normalizedStatus.value || "");
  return current === "completed" || current === "failed" || current === "cancelled";
});
const showCancelAction = computed(() => !isRunTerminal.value);
const cancelDisplay = computed(() => ({
  stopped_sessions: cancelResult.value?.stopped_sessions ?? 0,
  queue_fragments_detected: cancelResult.value?.queue_fragments_detected ?? 0,
  tracked_receipt_count: cancelResult.value?.tracked_receipt_count ?? 0
}));
function formatDurationMs(ms) {
  if (!Number.isFinite(ms) || ms <= 0) return "0s";
  const totalSeconds = Math.floor(ms / 1e3);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor(totalSeconds % 3600 / 60);
  const seconds = totalSeconds % 60;
  if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;
  if (minutes > 0) return `${minutes}m ${seconds}s`;
  return `${seconds}s`;
}
const runDurationMs = computed(() => {
  const createdAt = Number(status.value?.created_at || 0);
  const updatedAt = Number(status.value?.updated_at || 0);
  if (createdAt <= 0 || updatedAt <= 0) return 0;
  return Math.max(0, updatedAt - createdAt);
});
const runDurationLabel = computed(() => formatDurationMs(runDurationMs.value));
const nodeStats = computed(() => {
  const nodes = Object.values(status.value?.nodes ?? {});
  const stats = {
    total: nodes.length,
    completed: 0,
    failed: 0,
    running: 0,
    cancelled: 0,
    waiting: 0,
    retries: 0
  };
  for (const node of nodes) {
    const stateRaw = String(node?.state || "").toLowerCase();
    if (stateRaw === "done" || stateRaw === "completed") stats.completed += 1;
    else if (stateRaw === "error" || stateRaw === "failed") stats.failed += 1;
    else if (stateRaw === "cancelled" || stateRaw === "canceled") stats.cancelled += 1;
    else if (stateRaw === "running" || stateRaw === "active" || stateRaw === "queued") stats.running += 1;
    else stats.waiting += 1;
    const retries = Number(node?.retries || 0);
    if (Number.isFinite(retries) && retries > 0) stats.retries += retries;
  }
  return stats;
});
const statusQueueReceipts = computed(() => status.value?.queue_receipts ?? []);
const statusQueueReceiptCount = computed(() => statusQueueReceipts.value.length);
const statusQueueReceiptQueueCount = computed(() => new Set(statusQueueReceipts.value.map((item) => item.queue)).size);
const statusQueueReceiptNodeCount = computed(() => new Set(statusQueueReceipts.value.map((item) => item.node_uid)).size);
const loopOverviewStats = computed(() => {
  const loopStats = status.value?.loop_stats || {};
  const groups = loopGroups.value.groups;
  const loops = groups.length;
  const expandedLoops = groups.reduce((sum, group) => {
    const anyExpanded = group.nodeIds.some((nodeId) => Boolean(loopStats[nodeId]?.expanded));
    return sum + Number(anyExpanded);
  }, 0);
  const totalItems = groups.reduce((sum, group) => {
    const groupTotal = group.nodeIds.reduce((max, nodeId) => {
      return Math.max(max, Number(loopStats[nodeId]?.total_items || 0));
    }, 0);
    return sum + groupTotal;
  }, 0);
  const completedItems = groups.reduce((sum, group) => {
    const groupCompleted = group.nodeIds.reduce((max, nodeId) => {
      return Math.max(max, Number(loopStats[nodeId]?.completed_items || 0));
    }, 0);
    return sum + groupCompleted;
  }, 0);
  return {
    loops,
    expandedLoops,
    totalItems,
    completedItems
  };
});
const loopIndexOptions = computed(() => {
  const loopStats = status.value?.loop_stats || {};
  const selected = selectedStep.value;
  if (selected && selected.startsWith("loop-group:")) {
    const memberNodeIds = loopGroupNodeIdsByKey.value[selected] || [];
    const values2 = memberNodeIds.map((nodeId) => loopStats[nodeId]).filter(Boolean);
    const maxTotal2 = values2.reduce((max, entry) => Math.max(max, Number(entry?.total_items || 0)), 0);
    if (maxTotal2 <= 0) return [];
    const options2 = [{ value: "", label: "All loop items" }];
    for (let i = 0; i < maxTotal2; i++) {
      options2.push({ value: String(i), label: `Index #${i}` });
    }
    return options2;
  }
  if (selected && !loopStats[selected]) {
    return [];
  }
  const values = selected ? [loopStats[selected]].filter(Boolean) : Object.values(loopStats);
  const maxTotal = values.reduce((max, entry) => Math.max(max, Number(entry?.total_items || 0)), 0);
  if (maxTotal <= 0) return [];
  const options = [{ value: "", label: "All loop items" }];
  for (let i = 0; i < maxTotal; i++) {
    options.push({ value: String(i), label: `Index #${i}` });
  }
  return options;
});
const topologyStats = computed(() => {
  const levelArrays = Array.isArray(flowMeta.value?.analyzed?.levels) ? flowMeta.value?.analyzed?.levels : [];
  const levelCount = levelArrays.length;
  const maxParallelWidth = levelArrays.reduce((max, level) => Math.max(max, level.length), 0);
  const activeParallelLevels = levelArrays.filter((level) => level.length > 1).length;
  const nodeDefs = definition.value?.nodes || {};
  const nodeEntries = Object.entries(nodeDefs);
  const fanoutNodes = nodeEntries.filter(([, node]) => Boolean(node?.fanout)).length;
  const joinNodes = nodeEntries.filter(([, node]) => Array.isArray(node?.depends_on) && node.depends_on.length > 1).length;
  const terminalCandidates = nodeEntries.filter(([, node]) => {
    const emits = Array.isArray(node?.emits) ? node.emits.length : 0;
    return emits > 0;
  }).length;
  return {
    levelCount,
    maxParallelWidth,
    activeParallelLevels,
    fanoutNodes,
    joinNodes,
    terminalCandidates
  };
});
const formattedNodeResult = computed(() => {
  if (selectedNodeResult.value === null || selectedNodeResult.value === void 0) {
    return "null";
  }
  try {
    return JSON.stringify(selectedNodeResult.value, null, 2);
  } catch {
    return String(selectedNodeResult.value);
  }
});
</script>

<template>
  <div class="h-full flex flex-col overflow-hidden bg-zinc-50 dark:bg-zinc-950">
    <UModal v-model:open="isDeleteModalOpen" title="Delete Run">
      <template #body>
        <div class="space-y-2">
          <p class="text-sm text-zinc-700 dark:text-zinc-200">
            Diesen terminalen Run inklusive zugehoeriger Artefakte loeschen?
          </p>
          <p class="text-xs text-zinc-500 dark:text-zinc-400 font-mono">
            {{ runId }}
          </p>
          <p v-if="deleteError" class="text-xs text-red-600 dark:text-red-400">
            {{ deleteError }}
          </p>
        </div>
      </template>
      <template #footer>
        <div class="w-full flex justify-end gap-2">
          <UButton
            color="neutral"
            variant="ghost"
            label="Abbrechen"
            :disabled="deletePending"
            @click="isDeleteModalOpen = false"
          />
          <UButton
            color="error"
            variant="solid"
            label="Loeschen"
            :loading="deletePending"
            @click="confirmDeleteRun"
          />
        </div>
      </template>
    </UModal>

    <USlideover v-model:open="isResultSlideoverOpen" title="Node Result">
      <template #content>
        <div class="h-full flex flex-col bg-white dark:bg-zinc-950">
          <div class="px-4 py-3 border-b border-zinc-200 dark:border-zinc-800 space-y-2">
            <div class="text-xs text-zinc-500 dark:text-zinc-400">Step</div>
            <div class="text-sm font-medium text-zinc-900 dark:text-zinc-100 break-all">{{ selectedResultStepKey || "n/a" }}</div>
            <div v-if="resultNodeUidOptions.length > 1" class="space-y-1">
              <div class="text-xs text-zinc-500 dark:text-zinc-400">Loop item</div>
              <select
                v-model="selectedResultNodeUid"
                class="w-full text-xs rounded border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 px-2 py-1"
              >
                <option v-for="uid in resultNodeUidOptions" :key="uid" :value="uid">{{ uid }}</option>
              </select>
            </div>
          </div>

          <div class="flex-1 overflow-auto p-4">
            <div v-if="resultPending" class="text-sm text-zinc-500 dark:text-zinc-400">Loading result...</div>
            <div v-else-if="resultError" class="text-sm text-red-600 dark:text-red-400">{{ resultError }}</div>
            <div v-else-if="!selectedResultNodeUid" class="text-sm text-zinc-500 dark:text-zinc-400">No result available for this step yet.</div>
            <pre v-else class="text-xs leading-5 whitespace-pre-wrap break-words rounded border border-zinc-200 dark:border-zinc-800 bg-zinc-50 dark:bg-zinc-900 p-3">{{ formattedNodeResult }}</pre>
          </div>
        </div>
      </template>
    </USlideover>

    <!-- Header -->
    <div class="border-b border-zinc-200 dark:border-zinc-800 px-6 py-4 shrink-0 bg-white dark:bg-zinc-950">
      <div class="flex items-center justify-between w-full">
        <div class="flex items-center gap-4">
          <UButton
            icon="i-heroicons-chevron-left"
            color="gray"
            variant="ghost"
            @click="push(`/workflows/runs`)"
          />
          <div>
            <h1 class="text-xl font-bold text-zinc-900 dark:text-white flex items-center gap-2">
              Run: <span class="font-mono text-lg opacity-70">{{ (runId || "").slice(0, 8) }}...</span>
            </h1>
            <p class="text-xs text-zinc-500 dark:text-zinc-400">Execution detail and node status</p>
          </div>
        </div>
        <div class="flex items-center gap-2">
          <!-- State Slideover -->
          <USlideover 
            v-model="isStateSlideoverOpen"
            title="State Inspector">
            <UButton
              icon="i-lucide-database"
              color="neutral"
              variant="outline"
              label="State"
              @click="openStateSlideover"
            />
            <template #content>
              <NventFlowStateInspector
                :run-id="runId"
                :is-live="normalizedStatus === 'running' || normalizedStatus === 'awaiting' || normalizedStatus === 'awaiting_nodes'"
              />
            </template>
          </USlideover>

          <!-- Stream Slideover -->
          <USlideover
            v-model="isStreamSlideoverOpen"
            title="Stream Inspector">
            <UButton
              icon="i-lucide-waves"
              color="neutral"
              variant="outline"
              label="Streams"
              @click="openStreamSlideover"
            />
            <template #content>
              <NventFlowStreamInspector
                :run-id="runId"
                :is-live="normalizedStatus === 'running' || normalizedStatus === 'awaiting' || normalizedStatus === 'awaiting_nodes'"
              />
            </template>
          </USlideover>

          <USlideover
            v-model="isCancelSlideoverOpen"
            title="Run Controls"
          >
            <UButton
              icon="i-lucide-shield-alert"
              color="neutral"
              variant="outline"
              :label="normalizedStatus ? `Controls: ${String(normalizedStatus).toUpperCase()}` : 'Controls'"
              @click="openCancelSlideover"
            />
            <template #content>
              <div class="h-full flex flex-col p-4 gap-4 bg-white dark:bg-zinc-950 overflow-y-auto">
                <div class="rounded-xl border border-zinc-200 dark:border-zinc-800 bg-zinc-50/80 dark:bg-zinc-900/40 p-3">
                  <div class="flex items-center justify-between gap-3">
                    <div>
                      <div class="text-xs font-semibold text-zinc-900 dark:text-zinc-100">Workflow Cancel</div>
                      <div class="text-[11px] text-zinc-500 dark:text-zinc-400 mt-1">Run controls and queue cleanup diagnostics.</div>
                    </div>
                    <UBadge
                      v-if="normalizedStatus"
                      :label="String(normalizedStatus).toUpperCase()"
                      size="xs"
                      :color="normalizedStatus === 'completed' ? 'success' : normalizedStatus === 'failed' ? 'error' : 'neutral'"
                      variant="soft"
                    />
                  </div>

                  <div v-if="showCancelAction" class="mt-3">
                    <UButton
                      icon="i-lucide-x-circle"
                      color="error"
                      variant="solid"
                      :loading="cancelPending"
                      label="Cancel Run Now"
                      @click="cancelRun"
                    />
                  </div>
                  <div v-else class="mt-3 rounded-lg border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 px-3 py-2 text-[11px] text-zinc-600 dark:text-zinc-300">
                    This run is terminal ({{ String(normalizedStatus || "").toUpperCase() }}). Cancel action is no longer available.
                  </div>
                </div>

                <div v-if="cancelError" class="rounded-xl border border-red-200 dark:border-red-800 bg-red-50 dark:bg-red-950/30 p-3 text-xs text-red-700 dark:text-red-300">
                  {{ cancelError }}
                </div>

                <div
                  v-else-if="cancelResult || isRunCancelled"
                  class="rounded-xl border border-zinc-200 dark:border-zinc-800 bg-zinc-50/80 dark:bg-zinc-900/50 p-3"
                >
                  <div class="flex items-center justify-between gap-3">
                    <div class="text-xs font-semibold text-zinc-900 dark:text-zinc-100">
                      Cancel Execution Summary
                    </div>
                    <UBadge
                      :label="cancelResult ? cancelCleanupHasErrors ? 'Partial Cleanup' : 'Cleanup OK' : 'Already Cancelled'"
                      :color="cancelResult ? cancelCleanupHasErrors ? 'warning' : 'success' : 'neutral'"
                      size="xs"
                      variant="soft"
                    />
                  </div>

                  <div class="mt-3 grid grid-cols-2 gap-2">
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Sessions Stopped</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ cancelDisplay.stopped_sessions }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Queue Fragments</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ cancelDisplay.queue_fragments_detected }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Receipts Tracked</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ cancelDisplay.tracked_receipt_count }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Cleanup Success</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ cancelCleanupRate }}%</div>
                    </div>
                  </div>

                  <div class="mt-3 text-[11px] text-zinc-600 dark:text-zinc-300">
                    <template v-if="cancelResult">
                      Attempted {{ cancelCleanupAttempted }}, succeeded {{ cancelCleanupSucceeded }}, failed {{ cancelCleanupErrorEntries.length }}.
                    </template>
                    <template v-else>
                      No cleanup diagnostics available from this session.
                    </template>
                  </div>

                  <details v-if="cancelCheckedQueues.length > 0 || cancelTrackedReceiptIds.length > 0 || cancelCleanupHasErrors" class="mt-3 group">
                    <summary class="cursor-pointer text-xs font-medium text-zinc-700 dark:text-zinc-200 list-none flex items-center gap-2">
                      <span class="inline-block transition-transform group-open:rotate-90">▶</span>
                      Show Cleanup Details
                    </summary>
                    <div class="mt-2 space-y-2">
                      <div v-if="cancelCheckedQueues.length > 0" class="text-[11px] text-zinc-600 dark:text-zinc-300">
                        <span class="font-medium">Queues:</span>
                        {{ cancelCheckedQueues.join(", ") }}
                      </div>
                      <div v-if="cancelTrackedReceiptIds.length > 0" class="text-[11px] text-zinc-600 dark:text-zinc-300">
                        <span class="font-medium">Receipt IDs:</span>
                        {{ cancelTrackedReceiptIds.join(", ") }}
                      </div>
                      <div v-if="cancelCleanupHasErrors" class="rounded-lg border border-amber-200 dark:border-amber-800 bg-amber-50 dark:bg-amber-950/30 p-2">
                        <div class="text-[11px] font-semibold text-amber-800 dark:text-amber-300">Cleanup Errors</div>
                        <ul class="mt-1 space-y-1 text-[11px] text-amber-800/90 dark:text-amber-200/90 max-h-28 overflow-auto">
                          <li v-for="[receiptId, err] in cancelCleanupErrorEntries" :key="receiptId">
                            <span class="font-medium">{{ receiptId }}:</span> {{ err }}
                          </li>
                        </ul>
                      </div>
                    </div>
                  </details>
                </div>

                <div
                  v-if="isRunTerminal"
                  class="rounded-xl border border-zinc-200 dark:border-zinc-800 bg-zinc-50/80 dark:bg-zinc-900/50 p-3"
                >
                  <div class="flex items-center justify-between gap-3">
                    <div class="text-xs font-semibold text-zinc-900 dark:text-zinc-100">
                      Run Snapshot
                    </div>
                    <div class="flex items-center gap-2">
                      <UBadge
                        :label="String(normalizedStatus || 'terminal').toUpperCase()"
                        color="neutral"
                        size="xs"
                        variant="soft"
                      />
                      <UButton
                        icon="i-lucide-trash-2"
                        color="neutral"
                        variant="ghost"
                        size="xs"
                        title="Delete run"
                        :loading="deletePending"
                        @click="openDeleteModal"
                      />
                    </div>
                  </div>

                  <div class="mt-3 grid grid-cols-2 gap-2">
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Duration</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ runDurationLabel }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Nodes Total</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ nodeStats.total }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Nodes OK / Failed</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ nodeStats.completed }} / {{ nodeStats.failed }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Retries</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ nodeStats.retries }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Queue Receipts</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ statusQueueReceiptCount }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Run Result Mode</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ runResultStorageMode }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2 col-span-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Node Result Availability</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
                        store {{ resultOverview.readyStore }} · memory {{ resultOverview.readyMemory }} · stream {{ resultOverview.readyStream }}
                      </div>
                      <div class="text-[11px] text-zinc-600 dark:text-zinc-300 mt-1">
                        pending {{ resultOverview.pending }} · memory pruned {{ resultOverview.prunedMemory }}
                      </div>
                    </div>
                  </div>

                  <div class="mt-3 text-[11px] text-zinc-600 dark:text-zinc-300">
                    Receipt queues {{ statusQueueReceiptQueueCount }}, receipt nodes {{ statusQueueReceiptNodeCount }}.
                  </div>
                  <div v-if="status?.store_key" class="mt-2 text-[11px] text-zinc-600 dark:text-zinc-300 font-mono break-all">
                    store_key: {{ status.store_key }}
                  </div>
                  <div v-if="status?.result_error" class="mt-2 rounded border border-red-200 dark:border-red-900/40 bg-red-50 dark:bg-red-950/20 px-2 py-1.5 text-[11px] text-red-700 dark:text-red-300">
                    {{ status.result_error }}
                  </div>
                </div>

                <div
                  class="rounded-xl border border-zinc-200 dark:border-zinc-800 bg-zinc-50/80 dark:bg-zinc-900/50 p-3"
                >
                  <div class="flex items-center justify-between gap-3">
                    <div class="text-xs font-semibold text-zinc-900 dark:text-zinc-100">
                      Execution Topology
                    </div>
                    <UBadge
                      :label="`${topologyStats.maxParallelWidth}x max parallel`"
                      color="info"
                      size="xs"
                      variant="soft"
                    />
                  </div>

                  <div class="mt-3 grid grid-cols-2 gap-2">
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Depth (levels)</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ topologyStats.levelCount }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Parallel Levels</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ topologyStats.activeParallelLevels }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Join Nodes</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ topologyStats.joinNodes }}</div>
                    </div>
                    <div class="rounded-lg bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 px-2 py-2">
                      <div class="text-[10px] uppercase tracking-wide text-zinc-500">Fanout Nodes</div>
                      <div class="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{{ topologyStats.fanoutNodes }}</div>
                    </div>
                  </div>

                  <div class="mt-3 text-[11px] text-zinc-600 dark:text-zinc-300">
                    Potential terminal emitters: {{ topologyStats.terminalCandidates }}.
                  </div>
                </div>
              </div>
            </template>
          </USlideover>

           <UButton
             icon="i-heroicons-arrow-path"
             color="neutral"
             variant="outline"
             :loading="pending"
             @click="refreshAll"
           />
        </div>
      </div>
    </div>

    <div class="flex-1 overflow-hidden">
      <div class="h-full flex flex-col xl:flex-row overflow-hidden">
        <div class="min-w-0 flex-1 relative overflow-hidden border-b xl:border-b-0 xl:border-r border-zinc-200 dark:border-zinc-800">
        <div v-if="pending && !status" class="absolute inset-0 flex items-center justify-center bg-white/50 z-10 dark:bg-zinc-900/50">
           <div class="w-10 h-10 border-4 border-zinc-200 border-t-zinc-800 rounded-full animate-spin"></div>
        </div>
        
        <div v-else-if="error" class="p-12 text-center">
          <div class="inline-flex items-center justify-center w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/20 text-red-600 mb-4">
             <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
          </div>
          <p class="text-red-500 font-medium">Failed to load run status</p>
          <p class="text-sm text-zinc-500 mt-2">{{ error }}</p>
        </div>

        <div v-else class="h-full w-full">
          <NventFlowDiagram 
            v-if="flowMeta"
            height-class="h-full"
            :show-controls="true"
            :show-background="true"
            :flow="flowMeta"
            :step-states="stepStates"
            :flow-status="normalizedStatus"
          />
          <div v-else class="h-full flex items-center justify-center text-zinc-500">
             No diagram data available
          </div>
        </div>
        </div>

        <div class="w-full xl:w-[24rem] 2xl:w-[26rem] shrink-0 bg-white dark:bg-zinc-950 flex flex-col overflow-hidden border-b xl:border-b-0 xl:border-r border-zinc-200 dark:border-zinc-800">
          <NventFlowRunOverview
            v-if="status"
            :run-status="normalizedStatus"
            :run-id="runId"
            :steps="stepList"
            :started-at="status.created_at"
            :completed-at="status.updated_at"
            :loop-overview="loopOverviewStats"
            :result-overview="resultOverview"
            :run-result-mode="runResultStorageMode"
            :store-key="status.store_key"
            :result="status.result"
            :flow-def="flowMeta"
            @select-step="selectedStep = $event"
            @cancel-flow="cancelRun"
            @restart-flow="() => {
}"
            @inspect-step-result="openResultSlideover"
          />
          <div v-else-if="pending" class="p-8 space-y-4">
            <div class="h-8 bg-zinc-100 dark:bg-zinc-800 rounded animate-pulse w-1/2"></div>
            <div class="h-32 bg-zinc-100 dark:bg-zinc-800 rounded animate-pulse"></div>
            <div class="space-y-2">
              <div class="h-10 bg-zinc-100 dark:bg-zinc-800 rounded animate-pulse"></div>
              <div class="h-10 bg-zinc-100 dark:bg-zinc-800 rounded animate-pulse"></div>
            </div>
          </div>
        </div>

        <div class="w-full xl:w-[28rem] 2xl:w-[32rem] shrink-0 bg-white dark:bg-zinc-950 flex flex-col overflow-hidden">
          <NventFlowRunTimeline
            :run-id="runId"
            :run-status="normalizedStatus || 'unknown'"
            :started-at="status?.created_at"
            :completed-at="status?.updated_at"
            :node-checkpoints="status?.nodes"
            :selected-step="selectedStep"
            :selected-step-node-ids="selectedStepNodeIds"
            :loop-index-options="loopIndexOptions"
          />
        </div>
      </div>
    </div>
  </div>
</template>
